import Cesium, { Cartographic } from '../Ces/Cesium'
import WeDisplay from './WeDisplay'
import WeGeometryUtil from './WeGeometryUtil'
import WeUniform from './WeUniform'

class WeImageryLayerPicker {
  private display: WeDisplay
  private readState
  private outputTexture//: Cesium.Texture
  private command//: Cesium.ComputeCommand
  dirty: boolean = false
  private _callback: (results: number[]) => {} | undefined | null
  private batchTable//: Cesium.BatchTable
  private _listeningLocations: Cartographic[] = []
  private width: number = 64
  private height: number = 64
  public enable: boolean = true
  private cartesian3Scratch = new Cesium.Cartesian3()
  private _delayFrameNumber = 0

  constructor(options) {
    this.display = options.display
  }

  get listeningLocations() {
    return this._listeningLocations
  }

  set delayFrameNumber(value) {
    this._delayFrameNumber = value
  }

  get delayFrameNumber() {
    return this._delayFrameNumber
  }

  set callback(value) {
    this._callback = value
  }

  get callback() {
    return this._callback
  }

  getValueFromPixels(index: number, value: Float32Array): number[] {
    const i = index * 4
    const r = value[i + 0]
    const g = value[i + 1]
    const b = value[i + 2]
    return this.display.valueDecoder.isUV ? [r, g, b] : [r, g, b]
  }

  rebuild(context) {
    const { display, width, height } = this
    this.command?.shaderProgram.destroy()
    this.outputTexture?.destroy()
    this.outputTexture = new Cesium.Texture({
      context,
      width,
      height,
      pixelDatatype: Cesium.PixelDatatype.FLOAT,
      pixelFormat: Cesium.PixelFormat.RGBA,
      sampler: Cesium.Sampler.NEAREST,
    })

    this.readState = {
      framebuffer: new Cesium.Framebuffer({
        context,
        colorTextures: [this.outputTexture],
        destroyAttachments: false,
      }),
      x: 0,
      y: 0,
      width,
      height,
    };
    const numPointOfLine = 2
    const numVertexPerLine = numPointOfLine * 2
    const { vertexArray, attributeLocations } = WeGeometryUtil.createQuadGeometry(
      context, width, height, numPointOfLine, numVertexPerLine
    )

    const attributes = [{
      functionName: 'getQuerySTR',
      componentDatatype: Cesium.ComponentDatatype.FLOAT,
      componentsPerAttribute: 3,
    }];
    const batchTable = new Cesium.BatchTable(context, attributes, width * height)
    const cartesian3Scratch = this.cartesian3Scratch
    for (let i = 0; i < this._listeningLocations.length; i++) {
      const p = this._listeningLocations[i]
      cartesian3Scratch.x = p.longitude
      cartesian3Scratch.y = p.latitude
      cartesian3Scratch.z = p.height
      batchTable.setBatchedAttribute(i, 0, cartesian3Scratch)
    }

    this.batchTable = batchTable

    const weUniform = new WeUniform({
      name: 'WeImageryLayerPicker',
      floatUniform: {
        numLon: width,
        numLat: height,
        numPointOfLine,
        numVertexPerLine,
      }
    })


    const baseSource = display.getFragmentShaderSource(true, true, true)
    let vertexShaderSource = `
    precision highp float;
    ${baseSource}
    ${weUniform.getSource(true)}
     out vec3 v_textureCoordinates;
     void main()
     {
        vec4 extent = Weget_extent();
        float numLon = Weget_numLon();
        float numLat = Weget_numLat();
        float numPointOfLine = Weget_numPointOfLine();
        float numVertexPerLine = Weget_numVertexPerLine();
        float position = float(gl_VertexID);
        float lineID = floor(position / numVertexPerLine);
        vec3 location = getQuerySTR(lineID);

        float lineC = mod(lineID,numLon);
        float lineR = floor(lineID / numLon);

        float vertexID = mod(position, numVertexPerLine);
        float c = mod(vertexID,numPointOfLine);
        float r = floor(vertexID / numPointOfLine);
        float dx = 1.0 / (numLon);
        float dy = 1.0 / (numLat);

        vec2 uv = vec2((lineC + c) * dx,(lineR + r) * dy);
        vec2 p = vec2(-1.0) + uv * 2.0;

        vec3 str = vec3(0.0);
        str.x = (location.x - extent.x) / (extent.z - extent.x);
        str.y = (location.y - extent.y) / (extent.w - extent.y);
        str.z = location.z;
        str = fract(str);

        bool shouldFlipY = Weget_shouldFlipY() > 0.0;
        if(shouldFlipY){
          str.y = 1.0 - str.y;
        }

        v_textureCoordinates = location == vec3(0.0) ? vec3(0.0) : str;
        gl_Position = vec4(p,0.0,1.0);
     }
    `
    vertexShaderSource = batchTable.getVertexShaderCallback()(vertexShaderSource);

    const fragmentShaderSource = `
    ${baseSource}
    in vec3 v_textureCoordinates;
    void main()
    {
      vec3 str = v_textureCoordinates;
      vec4 comp = vec4(0.0);
      if(str != vec3(0.0))
      {
        comp = WeDecodeComponent(str);
      }
      float value = Weget_isUV() > 0.0 ? length(comp.xy) : comp.x;
      out_FragColor = vec4(value,comp.x,comp.y,1.0);
    }`;

    let uniformMap = {}
    uniformMap = batchTable.getUniformMapCallback()(uniformMap);
    display.setupUniformMap(uniformMap)

    weUniform.setupUniformMap(uniformMap)
    const computeCommand = new Cesium.ComputeCommand({
      vertexArray,
      shaderProgram: Cesium.ShaderProgram.fromCache({
        context,
        vertexShaderSource,
        fragmentShaderSource,
        attributeLocations
      }),
      outputTexture: this.outputTexture,
      uniformMap,
      persists: true,
      postExecute: () => this.postExecute(context)
    })

    this.command = computeCommand
  }

  postExecute(context) {
    const pixels = context.readPixels(this.readState);
    this._callback(pixels)
  }

  private reset() {
    this.outputTexture?.destroy()
    this.batchTable?.destroy()
    this.command?.shaderProgram.destroy()
  }

  setLocation(i: number, cartographic: Cartographic) {
    this._listeningLocations[i] = cartographic
    if (this.batchTable && i < this.batchTable.numberOfInstances) {
      const cartesian3Scratch = this.cartesian3Scratch
      cartesian3Scratch.x = cartographic.longitude
      cartesian3Scratch.y = cartographic.latitude
      cartesian3Scratch.z = cartographic.height
      this.batchTable.setBatchedAttribute(i, 0, cartesian3Scratch)
    } else {
      this.reset()
    }
    this.dirty = true
  }

  setListeningLocations(cartographics: Cartographic[]) {
    this._listeningLocations = cartographics
    this.dirty = true
  }

  update(frameState) {
    if (!frameState.passes.render || !this._callback || !this.enable) return
    if (this.dirty) {
      this.dirty = false
      if (!this.command) {
        this.rebuild(frameState.context)
      }
      this.batchTable.update(frameState)

      if(this.delayFrameNumber === 0
         || frameState.frameNumber % this.delayFrameNumber === 0){
        frameState.commandList.push(this.command)
      }
    }
  }

  destroy() {
    this.reset()
    return Cesium.destroyObject(this)
  }

  isDestroyed() {
    return false
  }
}

export default WeImageryLayerPicker
